package com.keyidea.jfxui.core.server.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.mina.core.buffer.IoBuffer;

import java.nio.ByteOrder;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;

@Slf4j
public class ByteUtils {

    /**
     * @Description: byte截取
     * @author zhongfei.
     * @date 2019/4/4 11:01
     * @ClassName: ByteUtils [src, begin, count]
     **/
    public static byte[] subBytes(byte[] src, int begin, int count) {
        byte[] bs = new byte[count];
        System.arraycopy(src, begin, bs, 0, count);
        return bs;
    }

    /**
     * 将16进制字符串转换为byte[]
     *
     * @param str
     * @return
     */
    public static byte[] toBytes(String str) {
        if ((str == null) || str.trim().equals("")) {
            return new byte[0];
        }

        byte[] bytes = new byte[str.length() / 2];
        for (int i = 0; i < (str.length() / 2); i++) {
            String subStr = str.substring(i * 2, (i * 2) + 2);
            bytes[i] = (byte) Integer.parseInt(subStr, 16);
        }

        return bytes;
    }

    public static byte[] byteMergerAll(byte[]... values) {
        int length_byte = 0;
        for (byte[] value : values) {
            length_byte += value.length;
        }
        byte[] all_byte = new byte[length_byte];
        int countLength = 0;
        for (byte[] b : values) {
            System.arraycopy(b, 0, all_byte, countLength, b.length);
            countLength += b.length;
        }
        return all_byte;
    }

    /**
     * @Description: 获取当前时间的byte数组 7字节
     * @author zhongfei.
     * @Param: []
     * @return: byte[]
     * @Date 2019/4/10 10:01
     */
    public static byte[] getNowDateByte() {
        Calendar cal = Calendar.getInstance();
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DATE);
        int hour = cal.get(Calendar.HOUR);
        int minute = cal.get(Calendar.MINUTE);
        int second = cal.get(Calendar.SECOND);

        byte[] yearByte = getNetBytes(year, 2);
        byte[] dateBytes = new byte[7];
        dateBytes[0] = yearByte[0];
        dateBytes[1] = yearByte[1];
        dateBytes[2] = (byte) month;
        dateBytes[3] = (byte) day;
        dateBytes[4] = (byte) hour;
        dateBytes[5] = (byte) minute;
        dateBytes[6] = (byte) second;
        return dateBytes;
    }

    /**
     * 获取属性字节序, 默认大端序（网络字节序）
     *
     * @param obj
     * @param len
     * @return
     * @author Juqiang Ma
     * @date Apr 11, 2019 3:13:40 PM
     * @version V1.0
     */
    public static byte[] getNetBytes(Object obj, int len) {
        return getNetBytes(obj, len, ByteOrder.BIG_ENDIAN);
    }

    public static byte[] getNetBytes(Object obj, int len, ByteOrder byteOrder) {
        IoBuffer ioBuffer = IoBuffer.allocate(1).order(byteOrder).setAutoExpand(true);

        switch (obj.getClass().getSimpleName().toUpperCase()) {
            case "SHORT" -> ioBuffer.putShort((short) obj);
            case "INTEGER", "INT" -> ioBuffer.putInt((int) obj);
            case "LONG" -> ioBuffer.putLong((long) obj);
            case "FLOAT" -> ioBuffer.putFloat((float) obj);
            case "DOUBLE" -> ioBuffer.putDouble((double) obj);
            case "STRING" -> ioBuffer.put(((String) obj).getBytes(StandardCharsets.UTF_8));
            case "BYTE" -> ioBuffer.put((byte) obj);
            default -> {
                return null;
            }
        }

        ioBuffer.flip();
        byte[] netBytes = new byte[ioBuffer.limit()];
        ioBuffer.get(netBytes, 0, ioBuffer.limit());

        if (len <= 0) {
            return netBytes;
        } else if (len < netBytes.length) {
            return subBytes(netBytes, netBytes.length - len, len);
        } else if (len == netBytes.length) {
            return netBytes;
        } else {
            byte[] outBytes = new byte[len];
            System.arraycopy(netBytes, 0, outBytes, 0, netBytes.length);
            return outBytes;
        }
    }

    /**
     * 获取给定字节数组的Hex字符串
     */
    public static String bytesToHex(byte[] data) {
        return IoBuffer.wrap(data).getHexDump();
    }

    /**
     * 获取给定Hex字符串的字节数组
     */
    public static byte[] hexToBytes(String hex) {
        String hexNoBlank = hex.replaceAll(" ", "");

        if (StringUtils.isEmpty(hexNoBlank)) {
            return null;
        }

        int dataLen = hexNoBlank.length() / 2;
        byte[] data = new byte[dataLen];

        for (int i = 0; i < dataLen; i++) {
            data[i] = (byte) Integer.parseInt(hexNoBlank.substring(i * 2, (i * 2) + 2), 16);
        }

        return data;
    }

    /**
     * 根据传入的 二进制数 的字符串形式表达 转换为对应的字节码
     */
    public static byte[] getBytesFromBinaryString(String binaryStr) {
        IoBuffer iBuf = IoBuffer.allocate(8).setAutoExpand(true);

        int count = (int) Math.ceil(binaryStr.length() * 1.0 / 64);

        for (int i = 0; i < count; i++) {
            long longNum = Long.parseUnsignedLong(binaryStr, 64 * i, 64 * (i + 1), 2);
            byte[] longBytes = getNetBytes(longNum, 8);
            iBuf.put(longBytes);
        }

        iBuf.flip();

        byte[] binaryStrBytes = new byte[iBuf.limit()];
        iBuf.get(binaryStrBytes, 0, iBuf.limit());

        return binaryStrBytes;
    }

    public static <T> T getData(byte[] msg, int offset, int len, Class<T> cla) {
        T data = null;
        try {
            data = getData(msg, offset, len, cla, ByteOrder.BIG_ENDIAN);
        } catch (CharacterCodingException e) {
            log.error(e.getMessage(), e);
        }
        return data;
    }

    public static <T> T getData(byte[] msg, int offset, int len, Class<T> cla, ByteOrder byteOrder) throws CharacterCodingException {
        IoBuffer ioBuffer = IoBuffer.allocate(1).order(byteOrder).setAutoExpand(true);
        ioBuffer.put(msg, offset, len);
        ioBuffer.flip();

        switch (cla.getSimpleName().toUpperCase()) {
            case "SHORT" -> {
                switch (len) {
                    case 1 -> {
                        return cla.cast(ioBuffer.getUnsigned());
                    }
                    case 2 -> {
                        return cla.cast(ioBuffer.getShort());
                    }
                    default -> {
                        return cla.cast(0);
                    }
                }
            }
            case "INTEGER", "INT" -> {
                switch (len) {
                    case 1 -> {
                        return cla.cast((int) ioBuffer.getUnsigned());
                    }
                    case 2 -> {
                        return cla.cast(ioBuffer.getUnsignedShort());
                    }
                    case 3, 4 -> {
                        return cla.cast(ioBuffer.getInt());
                    }
                    default -> {
                        return cla.cast(0);
                    }
                }
            }
            case "LONG" -> {
                switch (len) {
                    case 1 -> {
                        return cla.cast((long) ioBuffer.getUnsigned());
                    }
                    case 2 -> {
                        return cla.cast((long) ioBuffer.getUnsignedShort());
                    }
                    case 3, 4 -> {
                        return cla.cast(ioBuffer.getUnsignedInt());
                    }
                    case 5, 6, 7, 8 -> {
                        return cla.cast(ioBuffer.getLong());
                    }
                    default -> {
                        return cla.cast(0);
                    }
                }
            }
            case "FLOAT" -> {
                return cla.cast(ioBuffer.getFloat());
            }
            case "DOUBLE" -> {
                return cla.cast(ioBuffer.getDouble());
            }
            case "STRING" -> {
                return cla.cast(ioBuffer.getString(StandardCharsets.UTF_8.newDecoder()));
            }
            case "BYTE" -> {
                return cla.cast(ioBuffer.get());
            }
            default -> {
                return cla.cast(0);
            }
        }
    }
}
